//---------------------------------------------------------------------
// Author: Keith Hill, jachymko
//
// Description: Class to implement the Convet-LineEnding cmdlet which
//              converts line-endings to either Windows \r\n, Unix \n
//              or MacOs9 \r.
//
// Creation Date: Nov 12, 2006
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using System.Security;
using System.Text;
using Pscx.Commands.Internal;

namespace Pscx.Commands.Text
{
    public static class LineEnding
    {
        public const string Windows = "\r\n";
        public const string Unix    = "\n";
        public const string MacOs9  = "\r";
    }

    public abstract class ConvertToLineEndingBaseCommand : PscxPathCommandBase
    {
        private string _destination;
        private EncodingParameter _encoding;
        private SwitchParameter _force;
        private SwitchParameter _noClobber;

        [Parameter(Position = 1, Mandatory = true,
                   HelpMessage="Destination to write the converted file. If the destination is a directory, then the file is written to the directory using the same name."),
         AcceptsWildcards(false)]
        public string Destination
        {
            get { return _destination; }
            set { _destination = value; }
        }

        [ValidateNotNullOrEmpty]
        [Parameter(Position = 2, HelpMessage="Encoding used to write the output file. By default the encoding of the input file is used.  Valid values are: unicode, utf7, utf8, utf32, ascii and bigendianunicode")]
        [AcceptsWildcards(false)]
        public EncodingParameter Encoding
        {
            get { return _encoding; }
            set { _encoding = value; }
        }

        [Parameter(HelpMessage = "Overwrite any existing readonly file.")]
        public SwitchParameter Force
        {
            get { return _force; }
            set { _force = value; }
        }

        [Parameter(HelpMessage = "Specifies not to overwrite any existing file.")]
        public SwitchParameter NoClobber
        {
            get { return _noClobber; }
            set { _noClobber = value; }
        }

        protected abstract string TargetLineEnding { get; }

        protected override void BeginProcessing()
        {
            if (WildcardPattern.ContainsWildcardCharacters(_destination))
            {
                ArgumentException ex = new ArgumentException("Illegal characters in destination path");
                ThrowTerminatingError(new ErrorRecord(ex, "IllegalCharsInPath", ErrorCategory.InvalidArgument, _destination));
            }
            _destination = GetUnresolvedProviderPathFromPSPath(_destination);
        }

        protected override void ProcessPath(string path)
        {
            if (ShouldProcess(path))
            {
                ConvertLineEndingFromFile(path);
            }
        }

        private void ConvertLineEndingFromFile(string path)
        {            
            try
            {
                using (Stream input = File.Open(path, FileMode.Open, FileAccess.Read))
                {
                    using (StreamReader reader = new StreamReader(input, true))
                    {
                        Encoding inputEncoding = reader.CurrentEncoding;
                        Encoding outputEncoding = (_encoding.IsPresent) ? _encoding.ToEncoding() : inputEncoding;

                        using (Stream output = OpenOutputStream(path))
                        {
                            if (output == null) return;

                            using (TextWriter writer = new StreamWriter(output, outputEncoding))
                            {
                                ConvertLineEnding(reader, writer);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                WriteError(PscxErrorRecord.FileError(ex, path));
            }
        }

        private Stream OpenOutputStream(string path)
        {
            FileInfo fileInfo = null;
            bool resetToReadOnly = false;
            string outputPath = _destination;

            try
            {
                if (Directory.Exists(_destination))
                {
                    string filename = System.IO.Path.GetFileName(path);
                    outputPath = System.IO.Path.Combine(_destination, filename);
                }

                if (!_noClobber && _force && File.Exists(outputPath))
                {
                    fileInfo = new FileInfo(outputPath);

                    if (fileInfo.IsReadOnly)
                    {
                        fileInfo.IsReadOnly = false;
                        resetToReadOnly = true;
                    }
                }

                FileMode fileMode = _noClobber ? FileMode.CreateNew : FileMode.Create;

                return File.Open(outputPath, fileMode, FileAccess.Write);
            }
            catch (IOException ex)
            {
                WriteError(new ErrorRecord(ex, "FileAlreadyExists", ErrorCategory.InvalidOperation, outputPath));
            }
            catch (Exception ex)
            {
                WriteError(PscxErrorRecord.FileError(ex, outputPath));
            }
            finally
            {
                if (resetToReadOnly)
                {
                    fileInfo.IsReadOnly = true;
                }
            }

            return null;
        }

        private void ConvertLineEnding(TextReader reader, TextWriter writer)
        {
            char[] buffer = new char[4096];

            char? lastChar = null;
            int numRead;
            while ((numRead = reader.Read(buffer, 0, buffer.Length)) > 0)
            {
                for (int i = 0; i < numRead; i++)
                {
                    char? curChar = buffer[i];
                    if ((lastChar == LineEnding.Windows[0]) && (curChar == LineEnding.Windows[1]))
                    {
                        writer.Write(TargetLineEnding);
                        curChar = null;
                    }
                    else if ((lastChar == LineEnding.Unix[0]) || (lastChar == LineEnding.MacOs9[0]))
                    {
                        writer.Write(TargetLineEnding);
                    }
                    else if (lastChar.HasValue)
                    {
                        writer.Write(lastChar.Value);
                    }
                    lastChar = curChar;
                }
            }

            if (lastChar.HasValue)
            {
                if ((lastChar == LineEnding.Unix[0]) || (lastChar == LineEnding.MacOs9[0]))
                {
                    writer.Write(TargetLineEnding);
                }
                else
                {
                    writer.Write(lastChar.Value);
                }
            }
        }
    }
}
